package com.zyz.friend.service.user.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zyz.common.core.constants.CacheConstants;
import com.zyz.common.core.constants.Constants;
import com.zyz.common.core.constants.HttpConstants;
import com.zyz.common.core.enums.ResultCodeEnum;
import com.zyz.common.core.enums.UserIdentity;
import com.zyz.common.core.enums.UserStatus;
import com.zyz.common.core.model.LoginUser;
import com.zyz.common.core.model.LoginUserVO;
import com.zyz.common.core.model.Result;
import com.zyz.common.core.utils.ThreadLocalUtil;
import com.zyz.common.message.service.AliSmsService;
import com.zyz.common.redis.service.RedisService;
import com.zyz.common.security.exception.ServiceException;
import com.zyz.common.security.service.TokenService;
import com.zyz.friend.manager.UserCacheManager;
import com.zyz.friend.mapper.user.UserMapper;
import com.zyz.friend.model.user.User;
import com.zyz.friend.model.user.dto.UserCodeDTO;
import com.zyz.friend.model.user.dto.UserEditDTO;
import com.zyz.friend.model.user.dto.UserLoginDTO;
import com.zyz.friend.model.user.dto.UserUpHeadDTO;
import com.zyz.friend.model.user.vo.UserVO;
import com.zyz.friend.service.user.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
@RefreshScope
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AliSmsService aliSmsService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;

    @Value("${sms.send-limit:20}")
    private Integer sendLimit;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${sms.send-isSend:false}")
    private boolean isSend;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;


    @Override
    public void sendCode(UserCodeDTO userCodeDTO) {
        String phoneNumber = userCodeDTO.getPhone();
        // 校验手机号
        if (!checkPhone(phoneNumber)) {
            log.error("手机号输入不正确,手机号码: {}", phoneNumber);
            throw new ServiceException(ResultCodeEnum.FAILED_USER_PHONE_NUMBER);
        }
        // 获取上次验证码目前剩余的时间，不允许一分钟之内多次获取
        String phoneCodeKye = getPhoneCodeKye(phoneNumber);
        Long expireSeconds = redisService.getExpire(phoneCodeKye, TimeUnit.SECONDS);
        if (expireSeconds != null && phoneCodeExpiration * 60 - expireSeconds < 60 ) {
            // 禁止一分钟多次发送
            log.error("禁止一分钟多次发送验证码,phoneNumber: {}", phoneNumber);
            throw new ServiceException(ResultCodeEnum.FAILED_PHONE_CODE_AGAIN);
        }
        // 对于每一天的获取次数有一个限制，第二天计数清零
        // 获取redis中用户今天的访问次数
        String codeMaxCountKey = getPhoneCodeMaxCountKey(phoneNumber);
        Long codeCount = redisService.getCacheObject(codeMaxCountKey, Long.class);
        // 如果大于20次则禁止今天的发送
        if (codeCount != null && codeCount > sendLimit) {
            log.error("该用户已达到当天验证码发送上限,phoneNumber: {}", phoneNumber);
            throw new ServiceException(ResultCodeEnum.FAILED_PHONE_CODE_MAX_COUNT);
        }
        // 未达到当日上限，进行发送并且将记录当日发送的次数
        // 生成验证码
        String code = isSend?RandomUtil.randomNumbers(6): Constants.DEFAULT_CODE;
        // 发送验证码到用户手机上
        if (isSend) {
            aliSmsService.sendMobileCode(phoneNumber, code);
        }
        // 生成的验证码需要存储到redis中，为之后进行验证码的校验
        redisService.setCacheObject(phoneCodeKye, code,phoneCodeExpiration, TimeUnit.MINUTES);
        // 对该对象的当日验证码访问次数+1
        redisService.increment(codeMaxCountKey);
        if (codeCount == null) {
            // 当天第一次发送,设置一整天的过期时间
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeMaxCountKey,seconds,TimeUnit.SECONDS);
        }
    }

    @Override
    public String login(UserLoginDTO userLoginDTO) {
        String phone = userLoginDTO.getPhone();
        String code = userLoginDTO.getCode();
        if (!checkPhone(phone)) {
            log.error("手机号输入不正确,手机号码: {}", phone);
            throw new ServiceException(ResultCodeEnum.FAILED_USER_PHONE_NUMBER);
        }
        // 从redis中获取到验证码
        String cacheCode = redisService.getCacheObject(getPhoneCodeKye(phone), String.class);
        if (!code.equals(cacheCode)) {
            log.error("验证码不正确！userCode:{} cacheCode:{}", code, cacheCode);
            throw new ServiceException(ResultCodeEnum.FAILED_CODE_ERROR);
        }
        // 校验成功  删除redis中的验证码
        redisService.deleteObject(getPhoneCodeKye(phone));
        // 新用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if (user == null) {
            user = new User();
            Snowflake snowflake = new Snowflake();
            Long userId = snowflake.nextId();
            user.setUserId(userId);
            user.setPhone(phone);
            user.setNickName("用户" + RandomUtil.randomNumbers(6));
            user.setStatus(UserStatus.NORMAL.getStatus());
            user.setCreateBy(userId); // 手动赋值，因为login存储在拦截器中，所以从ThreadLocal里面拿不到
            int row = userMapper.insert(user);
            if (row != 1) {
                log.error("新增新用户失败,用户phone:{}", phone);
                throw new ServiceException(ResultCodeEnum.ERROR);
            }
        }
        // 将个人信息存入redis  并返回token
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(), user.getNickName(),user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        token = tokenStartCut(token);
        // 通过token获取key并删除redis中的用户信息
        return tokenService.deleteUserInfo(token,secret);
    }

    @Override
    public Result<LoginUserVO> info(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        token = tokenStartCut(token);
        // 根据token并调用redis拿到redis中的user信息
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if (loginUser == null) {
            return Result.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        }
        return Result.ok(loginUserVO);
    }

    @Override
    public UserVO detail() {
        // 从threadLocal中拿到用户id
        Long userId = ThreadLocalUtil.get(Constants.THREAD_LOCAL_USER_ID, Long.class);
        if (userId == null) {
            log.error("ThreadLocal中用户id为null");
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 从redis中获取用户详情
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            log.error("用户不存在，用户id: {}",userId);
            throw new ServiceException(ResultCodeEnum.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public void edit(UserEditDTO userEditDTO) {
        Long userId = ThreadLocalUtil.get(Constants.THREAD_LOCAL_USER_ID, Long.class);
        if (userId == null) {
            log.error("userId为null");
            throw new ServiceException(ResultCodeEnum.FAILED_USER_NOT_EXISTS);
        }
        User userByDB = userMapper.selectById(userId);
        if (userByDB == null) {
            log.error("userId为null");
            throw new ServiceException(ResultCodeEnum.FAILED_USER_NOT_EXISTS);
        }
        // 给数据库同步数据
        userByDB.setNickName(userEditDTO.getNickName());
        userByDB.setGender(userEditDTO.getGender());
        userByDB.setSchoolName(userEditDTO.getSchoolName());
        userByDB.setMajorName(userEditDTO.getMajorName());
        userByDB.setPhone(userEditDTO.getPhone());
        userByDB.setPhone(userEditDTO.getPhone());
        userByDB.setEmail(userEditDTO.getEmail());
        userByDB.setWechat(userEditDTO.getWechat());
        userByDB.setIntroduce(userEditDTO.getIntroduce());
        int row = userMapper.updateById(userByDB);
        if (row != 1) {
            log.error("用户信息修改失败");
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 更新redis中用户缓存
        userCacheManager.refreshUser(userByDB);
        // 更新redis中用户登录后的用户详情
        tokenService.refreshUserInfo(userByDB.getNickName(), userByDB.getHeadImage(),
                ThreadLocalUtil.get(Constants.THREAD_TOKEN_KEY,String.class));
        // 更新成功
    }

    @Override
    public void updateHeadImage(UserUpHeadDTO userUpHeadDTO) {
        String headImage = userUpHeadDTO.getHeadImage();
        Long userId = ThreadLocalUtil.get(Constants.THREAD_LOCAL_USER_ID, Long.class);
        if (userId == null) {
            log.error("userId为null");
            throw new ServiceException(ResultCodeEnum.FAILED_USER_NOT_EXISTS);
        }
        User userByDB = userMapper.selectById(userId);
        if (userByDB == null) {
            log.error("userId为null");
            throw new ServiceException(ResultCodeEnum.FAILED_USER_NOT_EXISTS);
        }
        // 同步数据给MySQL
        userByDB.setHeadImage(headImage);
        int row = userMapper.updateById(userByDB);
        if (row != 1) {
            log.error("头像更新存储失败");
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 更新redis中用户缓存
        userCacheManager.refreshUser(userByDB);
        // 更新redis中用户登录后的用户详情
        tokenService.refreshUserInfo(userByDB.getNickName(), userByDB.getHeadImage(),
                ThreadLocalUtil.get(Constants.THREAD_TOKEN_KEY,String.class));
        // 更新成功
    }


    private String tokenStartCut(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StringUtils.hasLength(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, "");
        }
        return token;
    }

    private String getPhoneCodeMaxCountKey (String phoneNumber) {
        return CacheConstants.PHONE_CODE_EVERYDAY_COUNT_KEY + phoneNumber;
    }

    private String getPhoneCodeKye(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[3456789]\\d{9}$");
        Matcher matcher = regex.matcher(phone);
        return matcher.matches();
    }
}
